1   /*
2    * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  /*
27   *
28   *  (C) Copyright IBM Corp. 1999 All Rights Reserved.
29   *  Copyright 1997 The Open Group Research Institute.  All rights reserved.
30   */
31  
32  package sun.security.krb5;
33  
34  import sun.security.krb5.internal.*;
35  import sun.security.util.*;
36  import java.net.*;
37  import java.util.Vector;
38  import java.io.IOException;
39  import java.math.BigInteger;
40  import sun.security.krb5.internal.ccache.CCacheOutputStream;
41  import sun.security.krb5.internal.util.KerberosString;
42  
43  
44  /**
45   * This class encapsulates a Kerberos principal.
46   */
47  public class PrincipalName
48      implements Cloneable {
49  
50      //name types
51  
52      /**
53       * Name type not known
54       */
55      public static final int KRB_NT_UNKNOWN =   0;
56  
57      /**
58       * Just the name of the principal as in DCE, or for users
59       */
60      public static final int KRB_NT_PRINCIPAL = 1;
61  
62      /**
63       * Service and other unique instance (krbtgt)
64       */
65      public static final int KRB_NT_SRV_INST =  2;
66  
67      /**
68       * Service with host name as instance (telnet, rcommands)
69       */
70      public static final int KRB_NT_SRV_HST =   3;
71  
72      /**
73       * Service with host as remaining components
74       */
75      public static final int KRB_NT_SRV_XHST =  4;
76  
77      /**
78       * Unique ID
79       */
80      public static final int KRB_NT_UID = 5;
81  
82  
83  
84      /**
85       * TGS Name
86       */
87      public static final String TGS_DEFAULT_SRV_NAME = "krbtgt";
88      public static final int TGS_DEFAULT_NT = KRB_NT_SRV_INST;
89  
90      public static final char NAME_COMPONENT_SEPARATOR = '/';
91      public static final char NAME_REALM_SEPARATOR = '@';
92      public static final char REALM_COMPONENT_SEPARATOR = '.';
93  
94      public static final String NAME_COMPONENT_SEPARATOR_STR = "/";
95      public static final String NAME_REALM_SEPARATOR_STR = "@";
96      public static final String REALM_COMPONENT_SEPARATOR_STR = ".";
97  
98      private int nameType;
99      private String[] nameStrings;  // Principal names don't mutate often
100 
101     private Realm nameRealm;  // optional; a null realm means use default
102     // Note: the nameRealm is not included in the default ASN.1 encoding
103 
104     // cached salt, might be changed by KDC info, not used in clone
105     private String salt = null;
106 
107     protected PrincipalName() {
108     }
109 
110     public PrincipalName(String[] nameParts, int type)
111         throws IllegalArgumentException, IOException {
112         if (nameParts == null) {
113             throw new IllegalArgumentException("Null input not allowed");
114         }
115         nameStrings = new String[nameParts.length];
116         System.arraycopy(nameParts, 0, nameStrings, 0, nameParts.length);
117         nameType = type;
118         nameRealm = null;
119     }
120 
121     public PrincipalName(String[] nameParts) throws IOException {
122         this(nameParts, KRB_NT_UNKNOWN);
123     }
124 
125     public Object clone() {
126         try {
127             PrincipalName pName = (PrincipalName) super.clone();
128             // Re-assign mutable fields
129             if (nameStrings != null) {
130                 pName.nameStrings = nameStrings.clone();
131             }
132             if (nameRealm != null) {
133                 pName.nameRealm = (Realm)nameRealm.clone();
134             }
135             return pName;
136         } catch (CloneNotSupportedException ex) {
137             throw new AssertionError("Should never happen");
138         }
139     }
140 
141     /*
142      * Added to workaround a bug where the equals method that takes a
143      * PrincipalName is not being called but Object.equals(Object) is
144      * being called.
145      */
146     public boolean equals(Object o) {
147         if (o instanceof PrincipalName)
148             return equals((PrincipalName)o);
149         else
150             return false;
151     }
152 
153     public boolean equals(PrincipalName other) {
154 
155 
156         if (!equalsWithoutRealm(other)) {
157             return false;
158         }
159 
160         if ((nameRealm != null && other.nameRealm == null) ||
161             (nameRealm == null && other.nameRealm != null)) {
162             return false;
163         }
164 
165         if (nameRealm != null && other.nameRealm != null) {
166             if (!nameRealm.equals(other.nameRealm)) {
167                 return false;
168             }
169         }
170 
171         return true;
172     }
173 
174     boolean equalsWithoutRealm(PrincipalName other) {
175 
176 
177         if (nameType != KRB_NT_UNKNOWN &&
178             other.nameType != KRB_NT_UNKNOWN &&
179             nameType != other.nameType)
180             return false;
181 
182         if ((nameStrings != null && other.nameStrings == null) ||
183             (nameStrings == null && other.nameStrings != null))
184             return false;
185 
186         if (nameStrings != null && other.nameStrings != null) {
187             if (nameStrings.length != other.nameStrings.length)
188                 return false;
189             for (int i = 0; i < nameStrings.length; i++)
190                 if (!nameStrings[i].equals(other.nameStrings[i]))
191                     return false;
192         }
193 
194         return true;
195 
196     }
197 
198     /**
199      * Returns the ASN.1 encoding of the
200      * <xmp>
201      * PrincipalName    ::= SEQUENCE {
202      *          name-type       [0] Int32,
203      *          name-string     [1] SEQUENCE OF KerberosString
204      * }
205      *
206      * KerberosString   ::= GeneralString (IA5String)
207      * </xmp>
208      *
209      * <p>
210      * This definition reflects the Network Working Group RFC 4120
211      * specification available at
212      * <a href="http://www.ietf.org/rfc/rfc4120.txt">
213      * http://www.ietf.org/rfc/rfc4120.txt</a>.
214      *
215      * @param encoding a Der-encoded data.
216      * @exception Asn1Exception if an error occurs while decoding
217      * an ASN1 encoded data.
218      * @exception Asn1Exception if there is an ASN1 encoding error
219      * @exception IOException if an I/O error occurs
220      * @exception IllegalArgumentException if encoding is null
221      * reading encoded data.
222      *
223      */
224     public PrincipalName(DerValue encoding)
225         throws Asn1Exception, IOException {
226         nameRealm = null;
227         DerValue der;
228         if (encoding == null) {
229             throw new IllegalArgumentException("Null input not allowed");
230         }
231         if (encoding.getTag() != DerValue.tag_Sequence) {
232             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
233         }
234         der = encoding.getData().getDerValue();
235         if ((der.getTag() & 0x1F) == 0x00) {
236             BigInteger bint = der.getData().getBigInteger();
237             nameType = bint.intValue();
238         } else {
239             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
240         }
241         der = encoding.getData().getDerValue();
242         if ((der.getTag() & 0x01F) == 0x01) {
243             DerValue subDer = der.getData().getDerValue();
244             if (subDer.getTag() != DerValue.tag_SequenceOf) {
245                 throw new Asn1Exception(Krb5.ASN1_BAD_ID);
246             }
247             Vector<String> v = new Vector<>();
248             DerValue subSubDer;
249             while(subDer.getData().available() > 0) {
250                 subSubDer = subDer.getData().getDerValue();
251                 v.addElement(new KerberosString(subSubDer).toString());
252             }
253             if (v.size() > 0) {
254                 nameStrings = new String[v.size()];
255                 v.copyInto(nameStrings);
256             } else {
257                 nameStrings = new String[] {""};
258             }
259         } else  {
260             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
261         }
262     }
263 
264     /**
265      * Parse (unmarshal) a <code>PrincipalName</code> from a DER
266      * input stream.  This form
267      * parsing might be used when expanding a value which is part of
268      * a constructed sequence and uses explicitly tagged type.
269      *
270      * @exception Asn1Exception on error.
271      * @param data the Der input stream value, which contains one or
272      * more marshaled value.
273      * @param explicitTag tag number.
274      * @param optional indicate if this data field is optional
275      * @return an instance of <code>PrincipalName</code>.
276      *
277      */
278     public static PrincipalName parse(DerInputStream data,
279                                       byte explicitTag, boolean
280                                       optional)
281         throws Asn1Exception, IOException {
282 
283         if ((optional) && (((byte)data.peekByte() & (byte)0x1F) !=
284                            explicitTag))
285             return null;
286         DerValue der = data.getDerValue();
287         if (explicitTag != (der.getTag() & (byte)0x1F))
288             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
289         else {
290             DerValue subDer = der.getData().getDerValue();
291             return new PrincipalName(subDer);
292         }
293     }
294 
295 
296     // This is protected because the definition of a principal
297     // string is fixed
298     // XXX Error checkin consistent with MIT krb5_parse_name
299     // Code repetition, realm parsed again by class Realm
300     protected static String[] parseName(String name) {
301 
302         Vector<String> tempStrings = new Vector<>();
303         String temp = name;
304         int i = 0;
305         int componentStart = 0;
306         String component;
307 
308         while (i < temp.length()) {
309             if (temp.charAt(i) == NAME_COMPONENT_SEPARATOR) {
310                 /*
311                  * If this separator is escaped then don't treat it
312                  * as a separator
313                  */
314                 if (i > 0 && temp.charAt(i - 1) == '\\') {
315                     temp = temp.substring(0, i - 1) +
316                         temp.substring(i, temp.length());
317                     continue;
318                 }
319                 else {
320                     if (componentStart < i) {
321                         component = temp.substring(componentStart, i);
322                         tempStrings.addElement(component);
323                     }
324                     componentStart = i + 1;
325                 }
326             } else
327                 if (temp.charAt(i) == NAME_REALM_SEPARATOR) {
328                     /*
329                      * If this separator is escaped then don't treat it
330                      * as a separator
331                      */
332                     if (i > 0 && temp.charAt(i - 1) == '\\') {
333                         temp = temp.substring(0, i - 1) +
334                             temp.substring(i, temp.length());
335                         continue;
336                     } else {
337                         if (componentStart < i) {
338                             component = temp.substring(componentStart, i);
339                             tempStrings.addElement(component);
340                         }
341                         componentStart = i + 1;
342                         break;
343                     }
344                 }
345             i++;
346         }
347 
348         if (i == temp.length())
349         if (componentStart < i) {
350             component = temp.substring(componentStart, i);
351             tempStrings.addElement(component);
352         }
353 
354         String[] result = new String[tempStrings.size()];
355         tempStrings.copyInto(result);
356         return result;
357     }
358 
359     public PrincipalName(String name, int type)
360         throws RealmException {
361         if (name == null) {
362             throw new IllegalArgumentException("Null name not allowed");
363         }
364         String[] nameParts = parseName(name);
365         Realm tempRealm = null;
366         String realmString = Realm.parseRealmAtSeparator(name);
367 
368         if (realmString == null) {
369             try {
370                 Config config = Config.getInstance();
371                 realmString = config.getDefaultRealm();
372             } catch (KrbException e) {
373                 RealmException re =
374                     new RealmException(e.getMessage());
375                 re.initCause(e);
376                 throw re;
377             }
378         }
379 
380         if (realmString != null)
381             tempRealm = new Realm(realmString);
382 
383         switch (type) {
384         case KRB_NT_SRV_HST:
385             if (nameParts.length >= 2) {
386                 String hostName = nameParts[1];
387                 try {
388                     // RFC4120 does not recommend canonicalizing a hostname.
389                     // However, for compatibility reason, we will try
390                     // canonicalize it and see if the output looks better.
391 
392                     String canonicalized = (InetAddress.getByName(hostName)).
393                             getCanonicalHostName();
394 
395                     // Looks if canonicalized is a longer format of hostName,
396                     // we accept cases like
397                     //     bunny -> bunny.rabbit.hole
398                     if (canonicalized.toLowerCase()
399                             .startsWith(hostName.toLowerCase()+".")) {
400                         hostName = canonicalized;
401                     }
402                 } catch (UnknownHostException e) {
403                     // no canonicalization, use old
404                 }
405                 nameParts[1] = hostName.toLowerCase();
406             }
407             nameStrings = nameParts;
408             nameType = type;
409                 // We will try to get realm name from the mapping in
410                 // the configuration. If it is not specified
411                 // we will use the default realm. This nametype does
412                 // not allow a realm to be specified. The name string must of
413                 // the form service@host and this is internally changed into
414                 // service/host by Kerberos
415 
416             String mapRealm =  mapHostToRealm(nameParts[1]);
417             if (mapRealm != null) {
418                 nameRealm = new Realm(mapRealm);
419             } else {
420                 nameRealm = tempRealm;
421             }
422             break;
423         case KRB_NT_UNKNOWN:
424         case KRB_NT_PRINCIPAL:
425         case KRB_NT_SRV_INST:
426         case KRB_NT_SRV_XHST:
427         case KRB_NT_UID:
428             nameStrings = nameParts;
429             nameType = type;
430             nameRealm = tempRealm;
431             break;
432         default:
433             throw new IllegalArgumentException("Illegal name type");
434         }
435     }
436 
437     public PrincipalName(String name) throws RealmException {
438         this(name, KRB_NT_UNKNOWN);
439     }
440 
441     public PrincipalName(String name, String realm) throws RealmException {
442         this(name, KRB_NT_UNKNOWN);
443         nameRealm = new Realm(realm);
444     }
445 
446     public String getRealmAsString() {
447         return getRealmString();
448     }
449 
450     public String getPrincipalNameAsString() {
451         StringBuffer temp = new StringBuffer(nameStrings[0]);
452         for (int i = 1; i < nameStrings.length; i++)
453             temp.append(nameStrings[i]);
454         return temp.toString();
455     }
456 
457     public int hashCode() {
458         return toString().hashCode();
459     }
460 
461     public String getName() {
462         return toString();
463     }
464 
465     public int getNameType() {
466         return nameType;
467     }
468 
469     public String[] getNameStrings() {
470         return nameStrings;
471     }
472 
473     public byte[][] toByteArray() {
474         byte[][] result = new byte[nameStrings.length][];
475         for (int i = 0; i < nameStrings.length; i++) {
476             result[i] = new byte[nameStrings[i].length()];
477             result[i] = nameStrings[i].getBytes();
478         }
479         return result;
480     }
481 
482     public String getRealmString() {
483         if (nameRealm != null)
484             return nameRealm.toString();
485         return null;
486     }
487 
488     public Realm getRealm() {
489         return nameRealm;
490     }
491 
492     public void setRealm(Realm new_nameRealm) throws RealmException {
493         nameRealm = new_nameRealm;
494     }
495 
496     public void setRealm(String realmsString) throws RealmException {
497         nameRealm = new Realm(realmsString);
498     }
499 
500     public String getSalt() {
501         if (salt == null) {
502             StringBuffer salt = new StringBuffer();
503             if (nameRealm != null) {
504                 salt.append(nameRealm.toString());
505             }
506             for (int i = 0; i < nameStrings.length; i++) {
507                 salt.append(nameStrings[i]);
508             }
509             return salt.toString();
510         }
511         return salt;
512     }
513 
514     public String toString() {
515         StringBuffer str = new StringBuffer();
516         for (int i = 0; i < nameStrings.length; i++) {
517             if (i > 0)
518                 str.append("/");
519             str.append(nameStrings[i]);
520         }
521         if (nameRealm != null) {
522             str.append("@");
523             str.append(nameRealm.toString());
524         }
525 
526         return str.toString();
527     }
528 
529     public String getNameString() {
530         StringBuffer str = new StringBuffer();
531         for (int i = 0; i < nameStrings.length; i++) {
532             if (i > 0)
533                 str.append("/");
534             str.append(nameStrings[i]);
535         }
536         return str.toString();
537     }
538 
539     /**
540      * Encodes a <code>PrincipalName</code> object.
541      * @return the byte array of the encoded PrncipalName object.
542      * @exception Asn1Exception if an error occurs while decoding an ASN1 encoded data.
543      * @exception IOException if an I/O error occurs while reading encoded data.
544      *
545      */
546     public byte[] asn1Encode() throws Asn1Exception, IOException {
547         DerOutputStream bytes = new DerOutputStream();
548         DerOutputStream temp = new DerOutputStream();
549         BigInteger bint = BigInteger.valueOf(this.nameType);
550         temp.putInteger(bint);
551         bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x00), temp);
552         temp = new DerOutputStream();
553         DerValue der[] = new DerValue[nameStrings.length];
554         for (int i = 0; i < nameStrings.length; i++) {
555             der[i] = new KerberosString(nameStrings[i]).toDerValue();
556         }
557         temp.putSequence(der);
558         bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x01), temp);
559         temp = new DerOutputStream();
560         temp.write(DerValue.tag_Sequence, bytes);
561         return temp.toByteArray();
562     }
563 
564 
565     /**
566      * Checks if two <code>PrincipalName</code> objects have identical values in their corresponding data fields.
567      *
568      * @param pname the other <code>PrincipalName</code> object.
569      * @return true if two have identical values, otherwise, return false.
570      */
571     // It is used in <code>sun.security.krb5.internal.ccache</code> package.
572     public boolean match(PrincipalName pname) {
573         boolean matched = true;
574         //name type is just a hint, no two names can be the same ignoring name type.
575         // if (this.nameType != pname.nameType) {
576         //      matched = false;
577         // }
578         if ((this.nameRealm != null) && (pname.nameRealm != null)) {
579             if (!(this.nameRealm.toString().equalsIgnoreCase(pname.nameRealm.toString()))) {
580                 matched = false;
581             }
582         }
583         if (this.nameStrings.length != pname.nameStrings.length) {
584             matched = false;
585         } else {
586             for (int i = 0; i < this.nameStrings.length; i++) {
587                 if (!(this.nameStrings[i].equalsIgnoreCase(pname.nameStrings[i]))) {
588                     matched = false;
589                 }
590             }
591         }
592         return matched;
593     }
594 
595     /**
596      * Writes data field values of <code>PrincipalName</code> in FCC format to an output stream.
597      *
598      * @param cos a <code>CCacheOutputStream</code> for writing data.
599      * @exception IOException if an I/O exception occurs.
600      * @see sun.security.krb5.internal.ccache.CCacheOutputStream
601      */
602     public void writePrincipal(CCacheOutputStream cos) throws IOException {
603         cos.write32(nameType);
604         cos.write32(nameStrings.length);
605         if (nameRealm != null) {
606             byte[] realmBytes = null;
607             realmBytes = nameRealm.toString().getBytes();
608             cos.write32(realmBytes.length);
609             cos.write(realmBytes, 0, realmBytes.length);
610         }
611         byte[] bytes = null;
612         for (int i = 0; i < nameStrings.length; i++) {
613             bytes = nameStrings[i].getBytes();
614             cos.write32(bytes.length);
615             cos.write(bytes, 0, bytes.length);
616         }
617     }
618 
619     /**
620      * Creates a KRB_NT_SRV_INST name from the supplied
621      * name components and realm.
622      * @param primary the primary component of the name
623      * @param instance the instance component of the name
624      * @param realm the realm
625      * @throws KrbException
626      */
627     protected PrincipalName(String primary, String instance, String realm,
628                             int type)
629         throws KrbException {
630 
631         if (type != KRB_NT_SRV_INST) {
632             throw new KrbException(Krb5.KRB_ERR_GENERIC, "Bad name type");
633         }
634 
635         String[] nParts = new String[2];
636         nParts[0] = primary;
637         nParts[1] = instance;
638 
639         this.nameStrings = nParts;
640         this.nameRealm = new Realm(realm);
641         this.nameType = type;
642     }
643 
644     /**
645      * Returns the instance component of a name.
646      * In a multi-component name such as a KRB_NT_SRV_INST
647      * name, the second component is returned.
648      * Null is returned if there are not two or more
649      * components in the name.
650      * @returns instance component of a multi-component name.
651      */
652     public String getInstanceComponent()
653     {
654         if (nameStrings != null && nameStrings.length >= 2)
655             {
656                 return new String(nameStrings[1]);
657             }
658 
659         return null;
660     }
661 
662     static String mapHostToRealm(String name) {
663         String result = null;
664         try {
665             String subname = null;
666             Config c = Config.getInstance();
667             if ((result = c.getDefault(name, "domain_realm")) != null)
668                 return result;
669             else {
670                 for (int i = 1; i < name.length(); i++) {
671                     if ((name.charAt(i) == '.') && (i != name.length() - 1)) { //mapping could be .ibm.com = AUSTIN.IBM.COM
672                         subname = name.substring(i);
673                         result = c.getDefault(subname, "domain_realm");
674                         if (result != null) {
675                             break;
676                         }
677                         else {
678                             subname = name.substring(i + 1);      //or mapping could be ibm.com = AUSTIN.IBM.COM
679                             result = c.getDefault(subname, "domain_realm");
680                             if (result != null) {
681                                 break;
682                             }
683                         }
684                     }
685                 }
686             }
687         } catch (KrbException e) {
688         }
689         return result;
690     }
691 
692 }